@file:Suppress("TYPEALIAS_EXPANSION_DEPRECATION", "DEPRECATION")

package com.script

import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.KJvmReplCompilerBase
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.currentLineId
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.JvmReplCompilerState
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplCodeAnalyzerBase
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.host.withDefaultsFrom
import kotlin.script.experimental.impl.internalScriptingRunSuspend
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
import kotlin.script.experimental.jvm.util.isIncomplete
import kotlin.script.experimental.util.PropertiesCollection

/**
 * @see kotlin.script.experimental.jvmhost.repl.JvmReplCompiler
 */
class JvmReplCompiler(
    val scriptCompilationConfiguration: ScriptCompilationConfiguration,
    val hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration
) : ReplCompilerWithoutCheck {

    override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> =
        JvmReplCompilerState({ KJvmReplCompilerBase.createCompilationState(it, hostConfiguration) }, lock)

    override fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult =
        state.lock.write {

            val replCompilerState = state.asState(JvmReplCompilerState::class.java)
            val snippet = codeLine.toSourceCode(scriptCompilationConfiguration)

            val replCompiler = KJvmReplCompilerBase<ReplCodeAnalyzerBase>(
                hostConfiguration.withDefaultsFrom(defaultJvmScriptingHostConfiguration),
                replCompilerState
            )

            val lineId = LineId(codeLine.no, codeLine.generation, snippet.hashCode())

            @Suppress("DEPRECATION_ERROR")
            val res = internalScriptingRunSuspend {
                replCompiler.compile(
                    listOf(snippet),
                    scriptCompilationConfiguration.with {
                        repl {
                            currentLineId(lineId)
                        }
                    }
                )
            }
            when (res) {
                is ResultWithDiagnostics.Success -> {
                    ReplCompileResult.CompiledClasses(
                        lineId,
                        replCompiler.state.history.map { it.id },
                        snippet.name!!,
                        emptyList(),
                        res.value.get().resultField != null,
                        emptyList(),
                        res.value.get().resultField?.second?.typeName,
                        res.value
                    )
                }

                else -> {
                    val message = res.reports.joinToString("\n")
                    if (res.isIncomplete()) {
                        ReplCompileResult.Incomplete(message)
                    } else {
                        ReplCompileResult.Error(message)
                    }
                }
            }
        }
}

internal class SourceCodeFromReplCodeLine(
    val codeLine: ReplCodeLine,
    compilationConfiguration: ScriptCompilationConfiguration
) : SourceCode {
    override val text: String get() = codeLine.code
    override val name: String =
        "${
            compilationConfiguration[ScriptCompilationConfiguration.repl.getScriptName]!!(
                ReplSnippetIdImpl(codeLine.no, codeLine.generation, 0), codeLine.scriptName
            )
        }.${compilationConfiguration[ScriptCompilationConfiguration.fileExtension]}"
    override val locationId: String? = null
}

internal fun ReplCodeLine.toSourceCode(
    compilationConfiguration: ScriptCompilationConfiguration
): SourceCode =
    SourceCodeFromReplCodeLine(this, compilationConfiguration)

typealias MakeSnippetIdentifier = (ReplSnippetId, String?) -> String

val ReplScriptCompilationConfigurationKeys.getScriptName by PropertiesCollection.key<MakeSnippetIdentifier>(
    { snippetId, _ ->
        makeDefaultSnippetIdentifier(snippetId)
    })

private val scriptNameMap = mutableMapOf<ReplCodeLine, String?>()

var ReplCodeLine.scriptName: String?
    get() = scriptNameMap[this]
    set(value) {
        scriptNameMap[this] = value
    }
